/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.server.setup;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContextAware;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.server.AbstractMockMvcBuilder;
import org.springframework.test.web.server.MockMvc;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.RequestToViewNameTranslator;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
/**
* Builds a {@link MockMvc} by instantiating the required Spring MVC components directly rather than detecting
* them in a Spring ApplicationContext. This makes it possible to build more "lightweight" and very focused tests
* involving one or just a few controllers.
*
* <p>The resulting setup is geared at supporting controllers with @{@link RequestMapping} methods. View resolution
* can be configured by providing a list of {@link ViewResolver}s. When view resolution is left not configured, a
* fixed, no-op {@link View} is used effectively ignoring rendering.
*
*/
public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder {
private final Object[] controllers;
private List<HttpMessageConverter<?>> messageConverters;
private Validator validator;
private FormattingConversionService conversionService = new DefaultFormattingConversionService();
private final List<MappedInterceptor> mappedInterceptors = new ArrayList<MappedInterceptor>();
private List<? extends ViewResolver> viewResolvers;
private GenericWebApplicationContext applicationContext;
protected StandaloneMockMvcBuilder(Object[] controllers) {
Assert.isTrue(!ObjectUtils.isEmpty(controllers), "At least one controller is required");
this.controllers = controllers;
this.applicationContext = new GenericWebApplicationContext(new MockServletContext());
this.applicationContext.refresh();
}
public StandaloneMockMvcBuilder setMessageConverters(HttpMessageConverter<?>...messageConverters) {
this.messageConverters = Arrays.asList(messageConverters);
return this;
}
public StandaloneMockMvcBuilder setValidator(Validator validator) {
this.validator = validator;
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(validator, "validator");
return this;
}
public StandaloneMockMvcBuilder setConversionService(FormattingConversionService conversionService) {
this.conversionService = conversionService;
return this;
}
public StandaloneMockMvcBuilder addInterceptors(HandlerInterceptor... interceptors) {
mapInterceptors(null, interceptors);
return this;
}
public StandaloneMockMvcBuilder mapInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) {
for (HandlerInterceptor interceptor : interceptors) {
mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor));
}
return this;
}
/**
* Configures a single ViewResolver that always renders using the provided View implementation.
* Provides a simple way to render generated content (e.g. JSON, XML, Atom, etc.) For URL-based view types,
* i.e. sub-classes of AbstractUrlBasedView, use {@link #setViewResolvers(ViewResolver...)} instead.
*
* @param view the default View to return for any view name
*/
public StandaloneMockMvcBuilder configureFixedViewResolver(View view) {
viewResolvers = Collections.singletonList(new FixedViewResolver(view));
return this;
}
/**
* Configures view resolution with the given {@link ViewResolver}s.
* By default, if no ViewResolvers have been configured, a View that doesn't do anything is used.
*
* <p>Most ViewResolver types should work as expected. This excludes {@link BeanNameViewResolver}
* since there is no ApplicationContext.
*
*/
public StandaloneMockMvcBuilder setViewResolvers(ViewResolver...resolvers) {
viewResolvers = Arrays.asList(resolvers);
return this;
}
@Override
protected WebApplicationContext initApplicationContext() {
return applicationContext;
}
@Override
protected List<? extends HandlerMapping> initHandlerMappings() {
StaticRequestMappingHandlerMapping mapping = new StaticRequestMappingHandlerMapping();
mapping.registerHandlers(controllers);
mapping.setInterceptors(mappedInterceptors.toArray());
return Collections.singletonList(mapping);
}
@Override
protected List<? extends HandlerAdapter> initHandlerAdapters() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
if (messageConverters != null) {
adapter.setMessageConverters(messageConverters);
}
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(conversionService);
initializer.setValidator(validator);
adapter.setWebBindingInitializer(initializer);
adapter.setApplicationContext(applicationContext); // for SpEL expressions in annotations
adapter.afterPropertiesSet();
return Collections.singletonList(adapter);
}
@Override
protected List<? extends HandlerExceptionResolver> initHandlerExceptionResolvers() {
ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver();
if (messageConverters != null) {
exceptionResolver.setMessageConverters( messageConverters);
}
exceptionResolver.afterPropertiesSet();
List<HandlerExceptionResolver> resolvers = new ArrayList<HandlerExceptionResolver>();
resolvers.add(exceptionResolver);
resolvers.add(new ResponseStatusExceptionResolver());
resolvers.add(new DefaultHandlerExceptionResolver());
return resolvers;
}
@Override
protected List<? extends ViewResolver> initViewResolvers() {
viewResolvers = (viewResolvers == null) ?
Arrays.asList(new FixedViewResolver(NOOP_VIEW)) : viewResolvers;
for (Object vr : viewResolvers) {
if (vr instanceof ApplicationContextAware) {
((ApplicationContextAware) vr).setApplicationContext(applicationContext);
}
}
return viewResolvers;
}
@Override
protected RequestToViewNameTranslator initViewNameTranslator() {
return new DefaultRequestToViewNameTranslator();
}
@Override
protected LocaleResolver initLocaleResolver() {
return new AcceptHeaderLocaleResolver();
}
/**
* Allows registering controller instances.
*/
private static class StaticRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
public void registerHandlers(Object...handlers) {
for (Object handler : handlers) {
super.detectHandlerMethods(handler);
}
}
}
/**
* Resolves all view names to the same fixed View.
*/
private static class FixedViewResolver implements ViewResolver {
private final View view;
public FixedViewResolver(View view) {
this.view = view;
}
public View resolveViewName(String viewName, Locale locale) throws Exception {
return view;
}
}
/**
* A View implementation that doesn't do anything.
*/
private static final View NOOP_VIEW = new View() {
public String getContentType() {
return null;
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
}
};
}